package com.leilei;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.symmetric.SM4;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author: lei
 * @date: 2024-07-18 10:12
 * @desc: 统一解密参数解析器
 */
public class DecryptBodyArgumentResolver implements HandlerMethodArgumentResolver {

    private final Logger logger = LoggerFactory.getLogger(DecryptBodyArgumentResolver.class);

    public static final String ENCRYPT_SECRET = "encrypt-secret";
    private final SecretProperties properties;

    public DecryptBodyArgumentResolver(SecretProperties secretProperties) {
        this.properties = secretProperties;
    }


    /**
     * 参数配置注解，才开启参数解析与解密
     *
     * @param parameter
     * @return boolean
     * @author lei
     * @date 2024-07-18 16:38:45
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(DecryptBody.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        // 从请求体读取数据
        String requestBody = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8))
                .lines()
                .collect(Collectors.joining("\n"));
        SecretRequestForm secretRequestForm = JSON.parseObject(requestBody, SecretRequestForm.class);
        Long outTimeStamp = secretRequestForm.getTimestamp();
        if (outTimeStamp == null || outTimeStamp > System.currentTimeMillis() || outTimeStamp < System.currentTimeMillis() - 1000L * 60 * properties.getValidDuration()) {
            throw new RuntimeException("请求已过期");
        }
        String secret = getSecret(secretRequestForm.getAppId());
        String decryptedData = decrypt(secret, secretRequestForm.getData());
        // 密钥信息存至请求上下文供响应加密使用
        request.setAttribute(ENCRYPT_SECRET, secret);
        if (StrUtil.isBlank(decryptedData)) {
            return null;
        }
        DecryptBody annotation = parameter.getParameterAnnotation(DecryptBody.class);
        if (annotation == null) {
            logger.error(decryptedData);
            throw new RuntimeException("DecryptBody注解必须指定解析class");
        }
        Class<?> targetClass = annotation.value();
        if (Collection.class.isAssignableFrom(parameter.getParameterType())) {
            // todo 内部时间校验
            return JSON.parseArray(decryptedData, targetClass);
        } else {
            Object parse = JSON.parse(decryptedData);
            JSONObject jsonObject = (JSONObject) parse;
            Long innerTimeStamp = jsonObject.getLong("timestamp");
            if (innerTimeStamp == null || !Objects.equals(outTimeStamp, innerTimeStamp)) {
                logger.error(decryptedData);
                throw new RuntimeException("请求已过期");
            }
            return jsonObject.toJavaObject(targetClass);
        }
    }

    /**
     * 获取密钥
     *
     * @param appId
     * @return String
     * @author lei
     * @date 2024-07-18 16:31:03
     */
    private String getSecret(String appId) {
        return Optional.ofNullable(SpringUtil.getBean(SecretService.class))
                .map(secretService -> secretService.getAppSecret(appId))
                .orElseThrow(() -> new RuntimeException("获取密钥失败"));
    }


    /**
     * 解密
     *
     * @param secret
     * @param encryptedData
     * @return String
     * @author lei
     * @date 2024-07-18 16:30:48
     */
    private String decrypt(String secret, String encryptedData) {
        if (StrUtil.isNotBlank(encryptedData)) {
            byte[] decode = Base64.decode(encryptedData);
            SM4 sm4 = SmUtil.sm4(secret.getBytes());
            return sm4.decryptStr(decode, StandardCharsets.UTF_8);
        }
        return encryptedData;
    }

}
